package com.ustc.hewei.rpc.client;

import com.ustc.hewei.rpc.client.loadbalance.ServiceGetter;
import com.ustc.hewei.rpc.client.loadbalance.UpdateService;
import com.ustc.hewei.rpc.client.net.impl.NettyRpcClient;
import com.ustc.hewei.rpc.common.Utils.MessageSerialize;
import com.ustc.hewei.rpc.common.protocol.*;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Resource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author hewei
 * @version 1.0
 * @description: 代理工厂，用于产生接口的代理对象并进行注入
 * @date 2022/7/28 19:34
 */

@Slf4j
public class ClientProxyFactory {

    //客户端网络，用于发送请求和接收响应
    private final NettyRpcClient rpcClient;

    private final ServiceGetter serviceGetter;

    // 服务调用重试的次数
    private final int retried;

    // 目前阻塞队列正在处理的失效服务列表
    private HashSet<Service> unService;

    public HashSet<Service> getUnService() {
        return unService;
    }

    public ClientProxyFactory(NettyRpcClient rpcClient, ServiceGetter serviceGetter, int retried) {
        unService = new HashSet<>();
        this.rpcClient = rpcClient;
        this.serviceGetter = serviceGetter;
        this.retried = retried;
    }

    public <T> T getProxy(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new RpcProxyHandler(clazz));
    }

    private class RpcProxyHandler implements InvocationHandler {
        private final Class<?> clazz;

        // 可重入锁，为了防止同一个服务对于阻塞队列的重复添加
        private final ReentrantLock lock;

        public <T> RpcProxyHandler(Class<T> clazz) {
            this.clazz = clazz;
            lock = new ReentrantLock();
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //跳过一些自带的默认方法
            if (method.getName().equals("toString")) {
                return proxy.getClass().toString();
            }
            if (method.getName().equals("hashCode")) return 0;

            // 失效次数
            int count = 0;

            String serviceName = clazz.getName();
            Service service = null;

            while (true) {
                try {
                    // 当达到了最大的重试次数，直接跳出，抛出异常
                    if (count == retried) break;

                    // 判读是否是第一次来调用，如果是第一次调用就正常，不是第一次就代表上一次失败的，就用nextService
                    if (unService.contains(service) || count != 0) {
                        service = serviceGetter.nextService(serviceName, count);
                    } else service = serviceGetter.getService(serviceName);

                    RequestWarp request = new RequestWarp().setMethod(method.getName()).setParams(args)
                            .setServiceName(serviceName).setParamsType(method.getParameterTypes())
                            .setRequestType(RequestType.MESSAGE);
                    byte[] bytes = MessageSerialize.objectToByteArray(request);
                    ResponseWarp responseWarp;
                    //发送请求，返回响应
                    byte[] response = rpcClient.sendRequest(bytes, service);
                    responseWarp = MessageSerialize.byteArrayToResp(response);
                    //对返回的响应状态进行判断
                    if (responseWarp.getStatus() == Status.SUCCESS) {
                        return responseWarp.getData();
                    } else if (responseWarp.getStatus() == Status.NOT_FOUND) {
                        log.error("服务端未找到对应服务！");
                        throw new RuntimeException("服务端未找到对应服务！");
                    } else {
                        log.error("服务端错误！");
                        throw new RuntimeException("服务端错误！");
                    }
                } catch (Exception e) {
                    // 重试次数加一
                    ++count;
                    // 加锁和判断该服务是否还在处理的双重验证
                    if (lock.tryLock() && !unService.contains(service)) {
                        try {
                            // 放入阻塞队列
                            unService.add(service);
                            serviceGetter.flushListAndGet(service);
                        } finally {
                            // 释放锁
                            lock.unlock();
                        }
                    }
                }
            }
            log.error("强制刷新缓存列表无效！");
            throw new RuntimeException("获取服务失败！");
        }
    }
}
